﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Linq;
using CommunityToolkit.WinUI.Lottie.WinCompData;
using CommunityToolkit.WinUI.Lottie.WinCompData.MetaData;
using CommunityToolkit.WinUI.Lottie.WinCompData.Mgce;
using CommunityToolkit.WinUI.Lottie.WinCompData.Mgcg;
using CommunityToolkit.WinUI.Lottie.WinUIXamlMediaData;
using Mgce = CommunityToolkit.WinUI.Lottie.WinCompData.Mgce;

namespace CommunityToolkit.WinUI.Lottie.UIData.CodeGen.Cx
{
#if PUBLIC_UIDataCodeGen
    public
#endif
    sealed class CxInstantiatorGenerator : InstantiatorGeneratorBase
    {
        const string Muxc = "Microsoft::UI::Xaml::Controls";
        readonly CxStringifier _s;
        readonly string _fileBaseName;
        readonly string _headerFileName;
        readonly string _cppFileName;
        readonly bool _isAnimatedIcon;

        // The name of the source class i.e. the class
        // that contains the TryCreateAnimatedVisual method.
        readonly string _sourceClassName;
        readonly string _wuc;
        readonly string _winUINamespace;

        // The fully qualified name of the AnimatedVisual type that is returned
        // from the TryCreateAnimatedVisual method.
        readonly string _animatedVisualTypeName;

        readonly string _animatedVisualTypeName2;

        /// <summary>
        /// Returns the Cx code for a factory that will instantiate the given <see cref="Visual"/> as a
        /// Windows.UI.Composition Visual.
        /// </summary>
        /// <returns>The result of the code generation.</returns>
        public static CxCodegenResult CreateFactoryCode(CodegenConfiguration configuration)
        {
            var generator = new CxInstantiatorGenerator(configuration, new CxStringifier());

            return new CxCodegenResult(
                cppFilename: generator._cppFileName,
                cppText: generator.GenerateCode(),
                hFilename: generator._headerFileName,
                hText: generator.GenerateHeaderText(),
                assets: generator.GetAssetsList()
            );
        }

        CxInstantiatorGenerator(CodegenConfiguration configuration, CxStringifier stringifier)
            : base(
                  configuration: configuration,
                  setCommentProperties: false,
                  stringifier)
        {
            _s = stringifier;
            _fileBaseName = $"{SourceInfo.Namespace}.{SourceInfo.ClassName}";
            _cppFileName = $"{_fileBaseName}.cpp";
            _headerFileName = $"{_fileBaseName}.h";
            _winUINamespace = SourceInfo.WinUIVersion.Major >= 3 ? "Microsoft::UI" : "Windows::UI";
            _wuc = $"{_winUINamespace}::Composition";
            _sourceClassName = SourceInfo.ClassName;
            _animatedVisualTypeName = Interface_IAnimatedVisual.GetQualifiedName(_s);
            _animatedVisualTypeName2 = Interface_IAnimatedVisual2.GetQualifiedName(_s);

            // Temporary until IAnimatedVisualSource2 makes it into WinUI3.
            _isAnimatedIcon = SourceInfo.WinUIVersion >= new Version(2, 6) && SourceInfo.WinUIVersion.Major < 3;
        }

        static string FieldAssignment(string fieldName) => fieldName is not null ? $"{fieldName} = " : string.Empty;

        IAnimatedVisualSourceInfo SourceInfo => AnimatedVisualSourceInfo;

        // Generates the .h file contents.
        string GenerateHeaderText()
        {
            // Returns the header text that implements IAnimatedVisualSource if loadedImageSurfacesNodes is null or empty.
            // Otherwise, return the header text that implements IDynamicAnimatedVisualSource.
            var builder = new HeaderBuilder();

            builder.Preamble.WriteLine("#pragma once");
            builder.Preamble.WriteLine(string.Join("\r\n", AutoGeneratedHeaderText));

            builder.Internal.Indent();
            builder.Internal.WriteLine("internal:");
            builder.Internal.Indent();

            builder.Private.Indent();
            builder.Private.Indent();

            builder.Public.Indent();
            builder.Public.WriteLine("public:");
            builder.Public.Indent();

            if (SourceInfo.LoadedImageSurfaces.Any())
            {
                WriteIDynamicAnimatedVisualSourceHeaderText(builder);
            }
            else
            {
                WriteIAnimatedVisualSourceHeaderText(builder);
            }

            // Close the class.
            builder.Public.CloseScopeWithSemicolon();

            // Close the namespace.
            builder.Postamble.Indent();
            builder.Postamble.CloseScope();
            return builder.ToString();
        }

        void WriteIAnimatedVisualSourceHeaderText(HeaderBuilder builder)
        {
            builder.Preamble.WriteLine();
            builder.Preamble.WriteLine($"namespace {_s.Namespace(SourceInfo.Namespace)}");
            builder.Preamble.OpenScope();

            WriteSourceDescriptionComments(builder.Preamble);

            var inherits = new List<string>();
            if (SourceInfo.GenerateDependencyObject)
            {
                inherits.Add($"{_winUINamespace}::Xaml::DependencyObject");
            }

            inherits.Add(Interface_IAnimatedVisualSource.GetQualifiedName(_s));

            if (SourceInfo.WinUIVersion >= new Version(2, 6) && SourceInfo.WinUIVersion.Major < 3)
            {
                inherits.Add(Interface_IAnimatedVisualSource2.GetQualifiedName(_s));
            }

            inherits.AddRange(SourceInfo.AdditionalInterfaces.Select(n => n.GetQualifiedName(_s)));

            WriteHeaderClassStart(builder.Preamble, inherits);

            WriteInternalHeaderConstants(builder.Internal);

            if (SourceInfo.IsThemed)
            {
                WriteThemeHeader(builder);
            }

            WriteTryCreateAnimatedVisualDecl(builder.Public);

            builder.Public.WriteLine();
            WriteMarkersPropertyDecl(builder.Public);

            builder.Public.WriteLine();
            WriteFrameCountDecl(builder.Public);

            builder.Public.WriteLine();
            WriteFramerateDecl(builder.Public);

            builder.Public.WriteLine();
            WriteDurationDecl(builder.Public);

            builder.Public.WriteLine();
            WriteFrameToProgressDecl(builder.Public);

            builder.Public.WriteLine();
            WriteSetColorPropertyDecl(builder.Public);

            builder.Public.WriteLine();
            WriteSetScalarPropertyDecl(builder.Public);
        }

        void WriteHeaderClassStart(CodeBuilder builder, IReadOnlyList<string> inherits)
        {
            builder.WriteLine($"public ref class {_sourceClassName} sealed");
            builder.Indent();
            builder.WriteLine($": public {inherits[0]}");
            for (var i = 1; i < inherits.Count; i++)
            {
                builder.WriteLine($", public {inherits[i]}");
            }

            builder.UnIndent();
            builder.OpenScope();
        }

        void WriteThemeHeader(HeaderBuilder builder)
        {
            // Add a field to hold the theme property set.
            builder.Private.WriteLine($"{_wuc}::CompositionPropertySet^ {SourceInfo.ThemePropertiesFieldName}{{ nullptr }};");

            builder.Internal.WriteComment("Theme properties.");

            var hasColorProperty = false;

            // Add fields and property declarations for each of the theme properties.
            foreach (var prop in SourceInfo.SourceMetadata.PropertyBindings)
            {
                hasColorProperty |= prop.ExposedType == PropertySetValueType.Color;

                if (SourceInfo.GenerateDependencyObject)
                {
                    builder.Private.WriteLine($"static {_winUINamespace}::Xaml::DependencyProperty^ _{_s.CamelCase(prop.BindingName)}Property;");
                    builder.Private.WriteLine($"static void On{prop.BindingName}Changed({_winUINamespace}::Xaml::DependencyObject^ d, {_winUINamespace}::Xaml::DependencyPropertyChangedEventArgs^ e);");
                    builder.Internal.WriteLine($"static {_winUINamespace}::Xaml::DependencyProperty^ {prop.BindingName}Property();");
                    builder.Internal.WriteLine();
                }
                else
                {
                    var exposedTypeName = QualifiedTypeName(prop.ExposedType);

                    WriteInitializedField(builder.Private, exposedTypeName, $"_theme{prop.BindingName}", _s.VariableInitialization($"c_theme{prop.BindingName}"));
                }

                builder.Internal.WriteLine($"property {QualifiedTypeName(prop.ExposedType)} {prop.BindingName}");
                builder.Internal.OpenScope();
                builder.Internal.WriteLine($"{QualifiedTypeName(prop.ExposedType)} get();");
                builder.Internal.WriteLine($"void set ({QualifiedTypeName(prop.ExposedType)} value);");
                builder.Internal.CloseScope();
                builder.Internal.WriteLine();
            }

            builder.Private.WriteLine();
            builder.Private.WriteLine($"{_wuc}::CompositionPropertySet^ EnsureThemeProperties({_wuc}::Compositor^ compositor);");
            builder.Private.WriteLine();

            if (hasColorProperty)
            {
                var b = builder.Internal;
                b.WriteLine($"static Windows::Foundation::Numerics::float4 ColorAsVector4(Windows::UI::Color color);");
                b.WriteLine();
            }
        }

        void WriteThemePropertyImpls(CodeBuilder builder)
        {
            var propertyBindings = SourceInfo.SourceMetadata.PropertyBindings;

            var sourceClassQualifier = $"{_s.Namespace(SourceInfo.Namespace)}::{_sourceClassName}::";

            if (propertyBindings.Any(pb => pb.ExposedType == PropertySetValueType.Color))
            {
                // Write the helper for converting a color to a vector 4.
                builder.WriteLine($"float4 {sourceClassQualifier}ColorAsVector4(Color color)");
                builder.OpenScope();
                builder.WriteLine("return { static_cast<float>(color.R), static_cast<float>(color.G), static_cast<float>(color.B), static_cast<float>(color.A) };");
                builder.CloseScope();
                builder.WriteLine();
            }

            builder.WriteLine($"CompositionPropertySet^ {sourceClassQualifier}EnsureThemeProperties(Compositor^ compositor)");
            builder.OpenScope();
            builder.WriteLine($"if ({SourceInfo.ThemePropertiesFieldName} == nullptr)");
            builder.OpenScope();
            builder.WriteLine($"{SourceInfo.ThemePropertiesFieldName} = compositor->CreatePropertySet();");

            // Initialize the values in the property set.
            foreach (var prop in propertyBindings)
            {
                WriteThemePropertyInitialization(builder, SourceInfo.ThemePropertiesFieldName, prop, prop.BindingName);
            }

            builder.CloseScope();
            builder.WriteLine();
            builder.WriteLine($"return {SourceInfo.ThemePropertiesFieldName};");
            builder.CloseScope();
            builder.WriteLine();

            // Write property implementations for each theme property.
            foreach (var prop in propertyBindings)
            {
                if (SourceInfo.GenerateDependencyObject)
                {
                    // Write the dependency property accessor.
                    builder.WriteLine($"DependencyProperty^ {sourceClassQualifier}{prop.BindingName}Property()");
                    builder.OpenScope();
                    builder.WriteLine($"return _{_s.CamelCase(prop.BindingName)}Property;");
                    builder.CloseScope();
                    builder.WriteLine();

                    // Write the dependency property change handler.
                    builder.WriteLine($"void {sourceClassQualifier}On{prop.BindingName}Changed(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e)");
                    builder.OpenScope();
                    builder.WriteLine($"auto self = ({sourceClassQualifier}^)d;");
                    builder.WriteLine();
                    builder.WriteLine($"if (self->{SourceInfo.ThemePropertiesFieldName} != nullptr)");
                    builder.OpenScope();
                    WriteThemePropertyInitialization(builder, $"self->{SourceInfo.ThemePropertiesFieldName}", prop, "e->NewValue");
                    builder.CloseScope();
                    builder.CloseScope();
                    builder.WriteLine();

                    // Write the dependency property initializer.
                    builder.WriteLine($"DependencyProperty^ {sourceClassQualifier}_{_s.CamelCase(prop.BindingName)}Property =");
                    builder.Indent();
                    builder.WriteLine($"DependencyProperty::Register(");
                    builder.Indent();
                    builder.WriteLine($"{_s.String(prop.BindingName)},");
                    builder.WriteLine($"{TypeName(prop.ExposedType)}::typeid,");
                    builder.WriteLine($"{sourceClassQualifier}typeid,");
                    builder.WriteLine($"ref new PropertyMetadata(c_theme{prop.BindingName},");
                    builder.WriteLine($"ref new PropertyChangedCallback(&{sourceClassQualifier}On{prop.BindingName}Changed)));");
                    builder.UnIndent();
                    builder.UnIndent();
                    builder.WriteLine();
                }

                // Write the getter.
                builder.WriteLine($"{TypeName(prop.ExposedType)} {sourceClassQualifier}{prop.BindingName}::get()");
                builder.OpenScope();
                if (SourceInfo.GenerateDependencyObject)
                {
                    // Get the value from the dependency property.
                    builder.WriteLine($"return ({TypeName(prop.ExposedType)})GetValue(_{_s.CamelCase(prop.BindingName)}Property);");
                }
                else
                {
                    // Get the value from the backing field.
                    builder.WriteLine($"return _theme{prop.BindingName};");
                }

                builder.CloseScope();
                builder.WriteLine();

                // Write the setter.
                builder.WriteLine($"void {sourceClassQualifier}{prop.BindingName}::set({TypeName(prop.ExposedType)} value)");
                builder.OpenScope();
                if (SourceInfo.GenerateDependencyObject)
                {
                    builder.WriteLine($"SetValue(_{_s.CamelCase(prop.BindingName)}Property, value);");
                }
                else
                {
                    // This saves to the backing field, and updates the theme property
                    // set if one has been created.
                    builder.WriteLine($"_theme{prop.BindingName} = value;");
                    builder.WriteLine($"if ({SourceInfo.ThemePropertiesFieldName} != nullptr)");
                    builder.OpenScope();
                    WriteThemePropertyInitialization(builder, SourceInfo.ThemePropertiesFieldName, prop);
                    builder.CloseScope();
                }

                builder.CloseScope();
                builder.WriteLine();
            }
        }

        void WriteIDynamicAnimatedVisualSourceHeaderText(HeaderBuilder builder)
        {
            var namespaces = new CppNamespaceListBuilder();
            namespaces.Add("Platform");
            namespaces.Add(Muxc);
            namespaces.Add($"{_winUINamespace}::Xaml");
            namespaces.Add($"{_winUINamespace}::Xaml::Data");
            namespaces.Add($"{_winUINamespace}::Xaml::Media");
            builder.Preamble.WriteCodeBuilder(namespaces.ToCodeBuilder());

            builder.Preamble.WriteLine();
            builder.Preamble.WriteLine($"namespace {_s.Namespace(SourceInfo.Namespace)}");
            builder.Preamble.OpenScope();

            WriteSourceDescriptionComments(builder.Preamble);

            var inherits = new List<string>();
            if (SourceInfo.GenerateDependencyObject)
            {
                inherits.Add($"{_winUINamespace}::Xaml::DependencyObject");
            }

            inherits.Add("IDynamicAnimatedVisualSourceSource");

            if (SourceInfo.WinUIVersion >= new Version(2, 6) && SourceInfo.WinUIVersion.Major < 3)
            {
                inherits.Add(Interface_IAnimatedVisualSource2.GetQualifiedName(_s));
            }

            inherits.Add("INotifyPropertyChanged");
            inherits.AddRange(SourceInfo.AdditionalInterfaces.Select(n => n.GetQualifiedName(_s)));

            WriteHeaderClassStart(builder.Preamble, inherits);

            WriteInternalHeaderConstants(builder.Internal);

            if (SourceInfo.IsThemed)
            {
                WriteThemeHeader(builder);
            }

            var pub = builder.Public;

            pub.WriteLine("virtual event Windows::Foundation::TypedEventHandler<IDynamicAnimatedVisualSource^, Object^>^ AnimatedVisualInvalidated;");
            pub.WriteLine();

            WriteTryCreateAnimatedVisualDecl(pub);
            pub.WriteLine();

            WriteFrameToProgressDecl(pub);
            pub.WriteLine();

            WriteMarkersPropertyDecl(pub);
            pub.WriteLine();

            pub.WriteLine($"virtual event PropertyChangedEventHandler^ PropertyChanged;");
            pub.WriteLine();

            pub.WriteComment("If this property is set to true, <see cref=\"TryCreateAnimatedVisual\"/> will" +
                " return null until all images have loaded. When all images have loaded, <see cref=\"TryCreateAnimatedVisual\"/>" +
                " will return the AnimatedVisual. To use, set it when instantiating the AnimatedVisualSource. Once" +
                " <see cref=\"TryCreateAnimatedVisual\"/> is called, changes made to this property will be ignored." +
                " Default value is false.");
            pub.WriteLine("property bool IsImageLoadingAsynchronous");
            pub.OpenScope();
            pub.WriteLine("bool get();");
            pub.WriteLine("void set(bool value);");
            pub.CloseScope();

            pub.WriteLine();
            pub.WriteComment("Returns true if all images have finished loading.");
            pub.WriteLine("property bool IsImageLoadingCompleted");
            pub.OpenScope();
            pub.WriteLine("bool get() { return m_isImageLoadingCompleted; }");
            pub.CloseScope();

            var priv = builder.Private;

            priv.WriteLine($"const int c_loadedImageSurfaceCount = {SourceInfo.LoadedImageSurfaces.Distinct().Count()};");
            priv.WriteLine("int m_loadCompleteEventCount{};");
            priv.WriteLine("bool m_isImageLoadingAsynchronous{};");
            priv.WriteLine("bool m_isImageLoadingCompleted{};");
            priv.WriteLine("bool m_isTryCreateAnimatedVisualCalled{};");
            priv.WriteLine("bool m_isImageLoadingStarted{};");

            foreach (var n in SourceInfo.LoadedImageSurfaces)
            {
                priv.WriteLine($"{n.TypeName}^ {n.FieldName}{{}};");
            }

            priv.WriteLine("void EnsureImageLoadingStarted();");
            priv.WriteLine("void HandleLoadCompleted(LoadedImageSurface^ sender, LoadedImageSourceLoadCompletedEventArgs^ e);");
        }

        /// <inheritdoc/>
        protected override void WriteAnimatedVisualStart(
            CodeBuilder builder,
            IAnimatedVisualInfo info)
        {
            // Start writing the instantiator.
            builder.WriteLine($"ref class {info.ClassName} sealed");
            builder.Indent();

            if (info.ImplementCreateAndDestroyMethods)
            {
                builder.WriteLine($": public {_animatedVisualTypeName2}");
            }
            else
            {
                builder.WriteLine($": public {_animatedVisualTypeName}");
            }

            builder.UnIndent();
            builder.OpenScope();

            if (SourceInfo.UsesCanvasEffects ||
                SourceInfo.UsesCanvasGeometry)
            {
                // D2D factory field.
                builder.WriteLine("ComPtr<ID2D1Factory> _d2dFactory;");
            }
        }

        void WriteInternalHeaderConstants(CodeBuilder builder)
        {
            // Add any internal constants.
            foreach (var c in SourceInfo.InternalConstants)
            {
                builder.WriteComment(c.Description);
                switch (c.Type)
                {
                    case ConstantType.Color:
                        builder.WriteLine($"static inline const Windows::UI::Color {c.Name}{_s.Color((WinCompData.Wui.Color)c.Value)};");
                        break;
                    case ConstantType.Int64:
                        builder.WriteLine($"static constexpr int64_t {c.Name}{{ {_s.Int64((long)c.Value)} }};");
                        break;
                    case ConstantType.Float:
                        builder.WriteLine($"static constexpr float {c.Name}{{ {_s.Float((float)c.Value)} }};");
                        break;
                    default:
                        throw new InvalidOperationException();
                }

                builder.WriteLine();
            }
        }

        /// <inheritdoc/>
        // Called by the base class to write the start of the file (i.e. everything up to the body of the Instantiator class).
        protected override void WriteImplementationFileStart(CodeBuilder builder)
        {
            builder.WriteLine("#include \"pch.h\"");
            builder.WriteLine($"#include \"{_headerFileName}\"");

            // floatY, floatYxZ
            builder.WriteLine("#include <WindowsNumerics.h>");

            if (SourceInfo.UsesCanvas ||
                SourceInfo.UsesCanvasEffects ||
                SourceInfo.UsesCanvasGeometry)
            {
                // D2D
                builder.WriteLine("#include <d2d1.h>");
                builder.WriteLine("#include <d2d1_1.h>");
                builder.WriteLine("#include <d2d1helper.h>");
                builder.WriteLine("#include <Windows.Graphics.Interop.h>");

                // Interop
                // BUILD_WINDOWS is defined if the code is being built as part of a Microsoft internal
                // Windows build. In that case the types in the Windows.Graphics.Effects.Interop.h file will
                // be in the Windows::Graphics::Effects namespace.
                //
                // Otherwise, the code is being built normally and the types will be in the
                // ABI::Windows::Graphics::Effects namespace.
                //
                // To work around this inconsistency, when BUILD_WINDOWS is defined, we wrap the include
                // of Windows.Graphics.Effects.Interop.h in the ABI namespace so that the types in that file
                // will always be in the ABI::Windows::Graphics::Effects namespace. And in our
                // generated code we always refer to the types in that file using the ABI:: prefix.
                builder.WriteLine("#ifdef BUILD_WINDOWS");
                builder.WriteLine("namespace ABI");
                builder.WriteLine("{");
                builder.WriteLine("#include <Windows.Graphics.Effects.Interop.h>");
                builder.WriteLine("}");
                builder.WriteLine("#else");
                builder.WriteLine("#include <Windows.Graphics.Effects.Interop.h>");
                builder.WriteLine("#endif");

                // ComPtr
                builder.WriteLine("#include <wrl.h>");
            }

            if (SourceInfo.UsesStreams)
            {
                builder.WriteLine("#include <iostream>");
            }

            if (SourceInfo.UsesCompositeEffect)
            {
                // The CompositeEffect class requires std::vector.
                builder.WriteLine("#include <vector>");
            }

            builder.WriteLine();

            var namespaces = new CppNamespaceListBuilder();

            namespaces.Add("Platform");
            namespaces.Add("Windows::Foundation");
            namespaces.Add("Windows::Foundation::Numerics");
            namespaces.Add($"{_winUINamespace}");
            namespaces.Add(_wuc);
            namespaces.Add("Windows::Graphics");
            namespaces.Add(Muxc);

            if (SourceInfo.UsesCanvas ||
                SourceInfo.UsesCanvasEffects ||
                SourceInfo.UsesCanvasGeometry)
            {
                namespaces.Add("Microsoft::WRL");
            }

            if (SourceInfo.UsesNamespaceWindowsUIXamlMedia)
            {
                namespaces.Add($"{_winUINamespace}::Xaml::Media");
            }

            if (SourceInfo.UsesStreams)
            {
                namespaces.Add("Platform");
                namespaces.Add("Windows::Storage::Streams");
            }

            if (SourceInfo.GenerateDependencyObject)
            {
                namespaces.Add($"{_winUINamespace}::Xaml");
            }

            builder.WriteCodeBuilder(namespaces.ToCodeBuilder());

            builder.WriteLine();

            // Put the Instantiator class in an anonymous namespace.
            builder.WriteLine("namespace");
            builder.WriteLine("{");
            builder.Indent();

            if (SourceInfo.UsesCanvasEffects ||
                SourceInfo.UsesCanvasGeometry)
            {
                // Write GeoSource to allow it's use in function definitions.
                builder.WriteLine(GeoSourceClass);

                // Typedef to simplify generation.
                builder.WriteLine("typedef ComPtr<GeoSource> CanvasGeometry;");
                builder.WriteLine();
            }

            if (SourceInfo.UsesCompositeEffect)
            {
                // Write the composite effect class that will allow the use
                // of this effect without win2d.
                builder.WriteLine(CompositeEffectClass);
            }

            if (SourceInfo.UsesGaussianBlurEffect)
            {
                // Write the Gaussian blur effect class that will allow the use
                // of this effect without win2d.
                builder.WriteLine(GaussianBlurEffectClass);
            }
        }

        /// <inheritdoc/>
        protected override string WriteCompositeEffectFactory(CodeBuilder builder, Mgce.CompositeEffect effect)
        {
            var effectVariable = "compositeEffect";
            builder.WriteLine($"ComPtr<CompositeEffect> {effectVariable}(new CompositeEffect());");
            builder.WriteLine($"{effectVariable}->SetMode({_s.CanvasCompositeMode(effect.Mode)});");
            foreach (var source in effect.Sources)
            {
                builder.OpenScope();
                builder.WriteLine($"auto sourceParameter = ref new CompositionEffectSourceParameter({_s.String(source.Name)});");
                builder.WriteLine($"{effectVariable}->AddSource(reinterpret_cast<ABI::Windows::Graphics::Effects::IGraphicsEffectSource*>(sourceParameter));");
                builder.CloseScope();
            }

            return $"reinterpret_cast<Windows::Graphics::Effects::IGraphicsEffect^>({effectVariable}.Get())";
        }

        /// <inheritdoc/>
        protected override string WriteGaussianBlurEffectFactory(CodeBuilder builder, GaussianBlurEffect effect)
        {
            var effectVariable = "gaussianBlurEffect";
            builder.WriteLine($"ComPtr<GaussianBlurEffect> {effectVariable}(new GaussianBlurEffect());");
            builder.WriteLine($"{effectVariable}->put_BlurAmount({_s.Float(effect.BlurAmount)});");

            builder.WriteLine($"auto sourceParameter = ref new CompositionEffectSourceParameter({_s.String(effect.Sources.First().Name)});");
            builder.WriteLine($"{effectVariable}->put_Source(reinterpret_cast<ABI::Windows::Graphics::Effects::IGraphicsEffectSource*>(sourceParameter));");

            return $"reinterpret_cast<Windows::Graphics::Effects::IGraphicsEffect^>({effectVariable}.Get())";
        }

        /// <inheritdoc/>
        protected override void WriteByteArrayField(CodeBuilder builder, string fieldName, IReadOnlyList<byte> bytes)
        {
            builder.WriteLine($"static const Array<byte>^ {fieldName} = ref new Array<byte>");
            builder.OpenScope();
            builder.WriteByteArrayLiteral(bytes, maximumColumns: 115);
            builder.UnIndent();
            builder.WriteLine("};");
        }

        /// <inheritdoc/>
        // Called by the base class to write the end of the file (i.e. everything after the body of the AnimatedVisual class).
        protected override void WriteImplementationFileEnd(CodeBuilder builder)
        {
            // Close the anonymous namespace.
            builder.UnIndent();
            builder.WriteLine("} // end namespace");
            builder.WriteLine();

            // Generate the methods that create and get the theme property set.
            if (SourceInfo.IsThemed)
            {
                WriteThemePropertyImpls(builder);
            }

            // Generate the overload of TryCreateAnimatedVisual that doesn't have diagnostics.
            builder.WriteLine($"{_animatedVisualTypeName}^ {_s.Namespace(SourceInfo.Namespace)}::{_sourceClassName}::TryCreateAnimatedVisual(");
            builder.Indent();
            builder.WriteLine($"Compositor^ compositor)");
            builder.UnIndent();
            builder.OpenScope();
            builder.WriteLine("return TryCreateAnimatedVisual(compositor, nullptr);");
            builder.CloseScope();
            builder.WriteLine();

            // Generate the method that creates an instance of the composition on the IAnimatedVisualSource.
            builder.WriteLine($"{_animatedVisualTypeName}^ {_s.Namespace(SourceInfo.Namespace)}::{_sourceClassName}::TryCreateAnimatedVisual(");
            builder.Indent();
            builder.WriteLine($"Compositor^ compositor,");
            builder.WriteLine($"Object^* diagnostics)");

            builder.UnIndent();
            builder.OpenScope();

            if (SourceInfo.IsThemed)
            {
                builder.WriteLine("const auto _ = EnsureThemeProperties(compositor);");
            }

            if (SourceInfo.LoadedImageSurfaces.Count > 0)
            {
                WriteIDynamicAnimatedVisualSource(builder);
            }
            else
            {
                WriteIAnimatedVisualSource(builder);
            }

            builder.WriteLine();

            WriteFrameCountImpl(builder);
            builder.WriteLine();

            WriteFramerateImpl(builder);
            builder.WriteLine();

            WriteDurationImpl(builder);
            builder.WriteLine();

            WriteFrameToProgressImpl(builder);
            builder.WriteLine();

            WriteMarkersPropertyImpl(builder);
            builder.WriteLine();

            WriteSetColorPropertyImpl(builder);
            builder.WriteLine();

            WriteSetScalarPropertyImpl(builder);
        }

        /// <summary>
        /// Generates the FrameCount property declaration.
        /// </summary>
        void WriteFrameCountDecl(CodeBuilder builder)
        {
            builder.WriteComment("Gets the number of frames in the animation.");
            builder.WriteLine($"property double FrameCount");
            builder.OpenScope();
            builder.WriteLine("double get();");
            builder.CloseScope();
        }

        /// <summary>
        /// Generates the FrameCount property implementation.
        /// </summary>
        void WriteFrameCountImpl(CodeBuilder builder)
        {
            builder.WriteLine($"double {_s.Namespace(SourceInfo.Namespace)}::{_sourceClassName}::FrameCount::get()");
            builder.OpenScope();
            builder.WriteLine($"return {_s.Double(SourceInfo.SourceMetadata.LottieMetadata.Duration.Frames)};");
            builder.CloseScope();
        }

        /// <summary>
        /// Generates the Framerate property declaration.
        /// </summary>
        void WriteFramerateDecl(CodeBuilder builder)
        {
            builder.WriteComment("Gets the framerate of the animation.");
            builder.WriteLine($"property double Framerate");
            builder.OpenScope();
            builder.WriteLine("double get();");
            builder.CloseScope();
        }

        /// <summary>
        /// Generates the Framerate property implementation.
        /// </summary>
        void WriteFramerateImpl(CodeBuilder builder)
        {
            builder.WriteLine($"double {_s.Namespace(SourceInfo.Namespace)}::{_sourceClassName}::Framerate::get()");
            builder.OpenScope();
            builder.WriteLine($"return {_s.Double(SourceInfo.SourceMetadata.LottieMetadata.Duration.FPS)};");
            builder.CloseScope();
        }

        /// <summary>
        /// Generates the Framerate property declaration.
        /// </summary>
        void WriteDurationDecl(CodeBuilder builder)
        {
            builder.WriteComment("Gets the duration of the animation.");
            builder.WriteLine($"property Windows::Foundation::TimeSpan Duration");
            builder.OpenScope();
            builder.WriteLine("Windows::Foundation::TimeSpan get();");
            builder.CloseScope();
        }

        /// <summary>
        /// Generates the Duration property implementation.
        /// </summary>
        void WriteDurationImpl(CodeBuilder builder)
        {
            builder.WriteLine($"TimeSpan {_s.Namespace(SourceInfo.Namespace)}::{_sourceClassName}::Duration::get()");
            builder.OpenScope();
            builder.WriteLine($"return {_s.TimeSpan(SourceInfo.SourceMetadata.LottieMetadata.Duration.Time)};");
            builder.CloseScope();
        }

        /// <summary>
        /// Generates the FrameToProgress(...) declaration.
        /// </summary>
        void WriteFrameToProgressDecl(CodeBuilder builder)
        {
            builder.WriteComment("Converts a zero-based frame number to the corresponding progress value denoting the start of the frame.");
            builder.WriteLine($"double FrameToProgress(double frameNumber);");
        }

        /// <summary>
        /// Generates the FrameToProgress(...) implementation.
        /// </summary>
        void WriteFrameToProgressImpl(CodeBuilder builder)
        {
            builder.WriteLine($"double {_s.Namespace(SourceInfo.Namespace)}::{_sourceClassName}::FrameToProgress(double frameNumber)");
            builder.OpenScope();
            builder.WriteLine($"return frameNumber / {_s.Double(SourceInfo.SourceMetadata.LottieMetadata.Duration.Frames)};");
            builder.CloseScope();
        }

        void WriteTryCreateAnimatedVisualDecl(CodeBuilder builder)
        {
            builder.WriteLine("[Windows::Foundation::Metadata::DefaultOverload]");
            builder.WriteLine($"{(_isAnimatedIcon ? "virtual " : string.Empty)}{_animatedVisualTypeName}^ TryCreateAnimatedVisual({_wuc}::Compositor^ compositor);");
            builder.WriteLine();

            builder.WriteLine($"virtual {_animatedVisualTypeName}^ TryCreateAnimatedVisual(");
            builder.Indent();
            builder.WriteLine($"{_wuc}::Compositor^ compositor,");
            builder.WriteLine($"Object^* diagnostics);");
            builder.UnIndent();
        }

        /// <summary>
        /// Generates the Markers property declaration.
        /// </summary>
        void WriteMarkersPropertyDecl(CodeBuilder builder)
        {
            builder.WriteComment("Returns a map from marker names to corresponding progress values.");
            builder.WriteLine($"{(_isAnimatedIcon ? "virtual " : string.Empty)}property Windows::Foundation::Collections::IMapView<Platform::String^, double>^ Markers");
            builder.OpenScope();
            builder.WriteLine("Windows::Foundation::Collections::IMapView<Platform::String^, double>^ get();");
            builder.CloseScope();
        }

        /// <summary>
        /// Generates the GetMarkerAsProgress(...) implementation.
        /// </summary>
        void WriteMarkersPropertyImpl(CodeBuilder builder)
        {
            builder.WriteLine($"Windows::Foundation::Collections::IMapView<String^, double>^ {_s.Namespace(SourceInfo.Namespace)}::{_sourceClassName}::Markers::get()");
            builder.OpenScope();

            builder.WriteLine("return (ref new Platform::Collections::Map<String^, double>(");
            builder.Indent();
            builder.OpenScope();
            foreach (var marker in SourceInfo.Markers)
            {
                builder.WriteLine($"{{ {_s.String(marker.Name)}, {_s.Double(marker.StartProgress)} }},");
            }

            builder.CloseScope();
            builder.UnIndent();
            builder.WriteLine("))->GetView();");
            builder.CloseScope();
        }

        void WriteSetColorPropertyDecl(CodeBuilder builder)
        {
            builder.WriteComment("Sets the color property with the given name, or does nothing if no such property exists.");
            builder.WriteLine($"{(_isAnimatedIcon ? "virtual " : string.Empty)}void SetColorProperty(Platform::String^ propertyName, Windows::UI::Color value);");
        }

        void WriteSetScalarPropertyDecl(CodeBuilder builder)
        {
            builder.WriteComment("Sets the scalar property with the given name, or does nothing if no such property exists.");
            builder.WriteLine("void SetScalarProperty(Platform::String^ propertyName, double value);");
        }

        void WriteSetColorPropertyImpl(CodeBuilder builder) =>
            WriteSetPropertyImpl(builder, PropertySetValueType.Color, "Color");

        void WriteSetScalarPropertyImpl(CodeBuilder builder) =>
            WriteSetPropertyImpl(builder, PropertySetValueType.Scalar, "double");

        void WriteSetPropertyImpl(CodeBuilder builder, PropertySetValueType propertyType, string typeName)
        {
            var properties = SourceInfo.SourceMetadata.PropertyBindings.Where(p => p.ExposedType == propertyType).ToArray();

            if (properties.Length == 0)
            {
                // There are no properties. The method doesn't need to do anything.
                builder.WriteLine($"void {_s.Namespace(SourceInfo.Namespace)}::{_sourceClassName}::Set{propertyType}Property(String^, {typeName})");
                builder.OpenScope();
                builder.CloseScope();
            }
            else
            {
                var propertySetTypeName = PropertySetValueTypeName(properties[0].ActualType);
                var valueInitializer = properties[0].ExposedType == PropertySetValueType.Color ? "ColorAsVector4(value)" : "value";

                builder.WriteLine($"void {_s.Namespace(SourceInfo.Namespace)}::{_sourceClassName}::Set{propertyType}Property(String^ propertyName, {typeName} value)");
                builder.OpenScope();

                var firstSeen = false;
                foreach (var prop in properties)
                {
                    // If the propertyName is a known name, save it into its backing field.
                    builder.WriteLine($"{(firstSeen ? "else " : string.Empty)}if (propertyName == {_s.String(prop.BindingName)})");
                    firstSeen = true;
                    builder.OpenScope();
                    builder.WriteLine($"_theme{prop.BindingName} = value;");
                    builder.CloseScope();
                }

                builder.WriteLine("else");
                builder.OpenScope();

                // Ignore the name if it doesn't refer to a known property.
                builder.WriteLine("return;");
                builder.CloseScope();
                builder.WriteLine();

                // Update the CompositionPropertySet if it has been created.
                builder.WriteLine($"if ({SourceInfo.ThemePropertiesFieldName} != nullptr)");
                builder.OpenScope();

                builder.WriteLine($"{SourceInfo.ThemePropertiesFieldName}->Insert{propertySetTypeName}(propertyName, {valueInitializer});");
                builder.CloseScope();
                builder.CloseScope();
            }
        }

        /// <summary>
        /// Generate the body of the TryCreateAnimatedVisual() method for a composition that does not contain LoadedImageSurfaces.
        /// </summary>
        void WriteIAnimatedVisualSource(CodeBuilder builder)
        {
            builder.WriteLine("if (diagnostics != nullptr) { diagnostics = nullptr; }");

            // Check the runtime version and instantiate the highest compatible IAnimatedVisual class.
            WriteInstantiateHighestCompatibleAnimatedVisual(builder, SourceInfo.AnimatedVisualInfos);

            builder.CloseScope();
        }

        IEnumerable<string> GetConstructorArguments(IAnimatedVisualInfo info)
        {
            yield return "compositor";

            if (SourceInfo.IsThemed)
            {
                yield return SourceInfo.ThemePropertiesFieldName;
            }

            foreach (var loadedImageSurfaceNode in info.LoadedImageSurfaceNodes)
            {
                yield return loadedImageSurfaceNode.FieldName;
            }
        }

        static string[] CommaSeparate(IEnumerable<string> args)
        {
            var result = args.ToArray();
            for (var i = 0; i < result.Length - 1; i++)
            {
                result[i] += ",";
            }

            return result;
        }

        static string TypeName(PropertySetValueType propertySetValueType)
            => propertySetValueType switch
            {
                PropertySetValueType.Color => "Color",
                PropertySetValueType.Scalar => "float",
                PropertySetValueType.Vector2 => "float2",
                PropertySetValueType.Vector3 => "float3",
                PropertySetValueType.Vector4 => "float4",
                _ => throw new InvalidOperationException()
            };

        // Write a description of the source as comments.
        void WriteSourceDescriptionComments(CodeBuilder builder) =>
            builder.WritePreformattedCommentLines(GetSourceDescriptionLines());

        void WriteInstantiateHighestCompatibleAnimatedVisual(
            CodeBuilder builder,
            IReadOnlyList<IAnimatedVisualInfo> animatedVisualInfos)
        {
            // WinUI3 doesn't ever do a version check. It's up to the user to make sure
            // the version they're using is compatible.
            if (SourceInfo.WinUIVersion.Major >= 3)
            {
                var info = animatedVisualInfos.First();
                builder.WriteBreakableLine($"auto result = {_s.New(info.ClassName)}(", CommaSeparate(GetConstructorArguments(info)), ");");
                if (info.ImplementCreateAndDestroyMethods)
                {
                    builder.WriteLine($"result.{CreateAnimationsMethod}();");
                }

                builder.WriteLine("return result;");
            }
            else
            {
                foreach (var info in animatedVisualInfos.OrderByDescending(avi => avi.RequiredUapVersion))
                {
                    builder.WriteLine();
                    builder.WriteLine($"if ({info.ClassName}::IsRuntimeCompatible())");
                    builder.OpenScope();
                    builder.WriteBreakableLine($"auto result = {_s.New(info.ClassName)}(", CommaSeparate(GetConstructorArguments(info)), ");");
                    if (info.ImplementCreateAndDestroyMethods)
                    {
                        builder.WriteLine($"result.{CreateAnimationsMethod}();");
                    }

                    builder.WriteLine("return result;");
                    builder.CloseScope();
                }

                builder.WriteLine();
                builder.WriteLine("return nullptr;");
            }
        }

        /// <summary>
        /// Generate the body of the TryCreateAnimatedVisual() method for a composition that contains LoadedImageSurfaces.
        /// </summary>
        void WriteIDynamicAnimatedVisualSource(CodeBuilder builder)
        {
            builder.WriteLine("m_isTryCreateAnimatedVisualCalled = true;");
            builder.WriteLine("if (diagnostics != nullptr) { *diagnostics = nullptr; }");
            builder.WriteLine();

            // Check whether the runtime will support the lowest UAP version required.
            var animatedVisualInfos = SourceInfo.AnimatedVisualInfos.OrderByDescending(avi => avi.RequiredUapVersion).ToArray();

            // WinUI3 doesn't ever do a version check. It's up to the user to make sure
            // the version they're using is compatible.
            if (SourceInfo.WinUIVersion.Major < 3)
            {
                builder.WriteLine($"if (!{animatedVisualInfos[^1].ClassName}::IsRuntimeCompatible())");
                builder.OpenScope();
                builder.WriteLine("return nullptr;");
                builder.CloseScope();
            }

            builder.WriteLine();
            builder.WriteLine("EnsureImageLoadingStarted();");
            builder.WriteLine();
            builder.WriteLine("if (m_isImageLoadingAsynchronous && m_loadCompleteEventCount != c_loadedImageSurfaceCount)");
            builder.OpenScope();
            builder.WriteLine("return nullptr;");
            builder.CloseScope();

            // Check the runtime version and instantiate the highest compatible IAnimatedVisual class.
            WriteInstantiateHighestCompatibleAnimatedVisual(builder, animatedVisualInfos);

            builder.CloseScope();
            builder.WriteLine();

            // Generate the get() and set() methods of IsImageLoadingAsynchronous property.
            WriteIsImageLoadingAsynchronousGetSet(builder);

            // Generate the method that loads all the LoadedImageSurfaces.
            WriteEnsureImageLoadingStarted(builder);

            // Generate the method that handles the LoadCompleted event of the LoadedImageSurface objects.
            WriteHandleLoadCompleted(builder);
        }

        void WriteIsImageLoadingAsynchronousGetSet(CodeBuilder builder)
        {
            builder.WriteLine($"bool {_s.Namespace(SourceInfo.Namespace)}::{_sourceClassName}::IsImageLoadingAsynchronous::get()");
            builder.OpenScope();
            builder.WriteLine("return m_isImageLoadingAsynchronous;");
            builder.CloseScope();
            builder.WriteLine();
            builder.WriteLine($"void {_s.Namespace(SourceInfo.Namespace)}::{_sourceClassName}::IsImageLoadingAsynchronous::set(bool value)");
            builder.OpenScope();
            builder.WriteLine("if (!m_isTryCreateAnimatedVisualCalled && m_isImageLoadingAsynchronous != value)");
            builder.OpenScope();
            builder.WriteLine("m_isImageLoadingAsynchronous = value;");
            builder.WriteLine("PropertyChanged(this, ref new PropertyChangedEventArgs(\"IsImageLoadingAsynchronous\"));");
            builder.CloseScope();
            builder.CloseScope();
            builder.WriteLine();
        }

        void WriteEnsureImageLoadingStarted(CodeBuilder builder)
        {
            builder.WriteLine($"void {_s.Namespace(SourceInfo.Namespace)}::{_sourceClassName}::EnsureImageLoadingStarted()");
            builder.OpenScope();
            builder.WriteLine("if (!m_isImageLoadingStarted)");
            builder.OpenScope();
            builder.WriteLine($"auto eventHandler = ref new TypedEventHandler<LoadedImageSurface^, LoadedImageSourceLoadCompletedEventArgs^>(this, &{_s.Namespace(SourceInfo.Namespace)}::{_sourceClassName}::HandleLoadCompleted);");

            foreach (var n in SourceInfo.LoadedImageSurfaces)
            {
                var imageMemberName = n.FieldName;
                switch (n.LoadedImageSurfaceType)
                {
                    case LoadedImageSurface.LoadedImageSurfaceType.FromStream:
                        var streamName = $"stream_{n.Name}";
                        var dataWriterName = $"dataWriter_{n.Name}";
                        builder.WriteLine($"auto {streamName} = ref new InMemoryRandomAccessStream();");
                        builder.WriteLine($"auto {dataWriterName} = ref new DataWriter({streamName}->GetOutputStreamAt(0));");
                        builder.WriteLine($"{dataWriterName}->WriteBytes({n.BytesFieldName});");
                        builder.WriteLine($"{dataWriterName}->StoreAsync();");
                        builder.WriteLine($"{dataWriterName}->FlushAsync();");
                        builder.WriteLine($"{streamName}->Seek(0);");
                        builder.WriteLine($"{imageMemberName} = {_winUINamespace}::Xaml::Media::LoadedImageSurface::StartLoadFromStream({streamName});");
                        break;
                    case LoadedImageSurface.LoadedImageSurfaceType.FromUri:
                        builder.WriteComment(n.Comment);
                        builder.WriteLine($"{imageMemberName} = {_winUINamespace}::Xaml::Media::LoadedImageSurface::StartLoadFromUri(ref new Uri(\"{n.ImageUri}\"));");
                        break;
                    default:
                        throw new InvalidOperationException();
                }

                builder.WriteLine($"{imageMemberName}->LoadCompleted += eventHandler;");
            }

            builder.WriteLine("m_isImageLoadingStarted = true;");
            builder.CloseScope();
            builder.CloseScope();
            builder.WriteLine();
        }

        void WriteHandleLoadCompleted(CodeBuilder builder)
        {
            builder.WriteLine($"void {_s.Namespace(SourceInfo.Namespace)}::{_sourceClassName}::HandleLoadCompleted(LoadedImageSurface^ sender, LoadedImageSourceLoadCompletedEventArgs^ e)");
            builder.OpenScope();
            builder.WriteLine("m_loadCompleteEventCount++;");
            builder.WriteLine();
            builder.WriteLine("if (m_loadCompleteEventCount == c_loadedImageSurfaceCount)");
            builder.OpenScope();
            builder.WriteLine("m_isImageLoadingCompleted = true;");
            builder.WriteLine("PropertyChanged(this, ref new PropertyChangedEventArgs(\"IsImageLoadingCompleted\"));");

            // If asynchronouse image loading is enabled notify via IDynamicAnimatedVisualSource that
            // the previous result is now invalidated.
            builder.WriteLine("if (m_isImageLoadingAsynchronous)");
            builder.OpenScope();
            builder.WriteLine("AnimatedVisualInvalidated(this, nullptr);");
            builder.CloseScope();
            builder.CloseScope();
            builder.CloseScope();
            builder.WriteLine();
        }

        void WriteIsRuntimeCompatibleMethod(CodeBuilder builder, IAnimatedVisualInfo info)
        {
            // WinUI3 doesn't ever do a version check. It's up to the user to make sure
            // the version they're using is compatible.
            if (SourceInfo.WinUIVersion.Major < 3)
            {
                // Write the IsRuntimeCompatible static method.
                builder.WriteLine("static bool IsRuntimeCompatible()");
                builder.OpenScope();
                builder.WriteLine($"return Windows::Foundation::Metadata::ApiInformation::IsApiContractPresent({_s.String("Windows.Foundation.UniversalApiContract")}, {info.RequiredUapVersion});");
                builder.CloseScope();
            }
        }

        /// <inheritdoc/>
        // Called by the base class to write the end of the AnimatedVisual class.
        protected override void WriteAnimatedVisualEnd(
            CodeBuilder builder,
            IAnimatedVisualInfo info,
            CodeBuilder createAnimations,
            CodeBuilder destroyAnimations)
        {
            if (SourceInfo.UsesCanvasEffects ||
                SourceInfo.UsesCanvasGeometry)
            {
                // Utility method for D2D geometries.
                builder.WriteLine("static IGeometrySource2D^ CanvasGeometryToIGeometrySource2D(CanvasGeometry geo)");
                builder.OpenScope();
                builder.WriteLine("ComPtr<ABI::Windows::Graphics::IGeometrySource2D> interop = geo.Detach();");
                builder.WriteLine("return reinterpret_cast<IGeometrySource2D^>(interop.Get());");
                builder.CloseScope();
                builder.WriteLine();

                // Utility method for fail-fasting on bad HRESULTs from d2d operations.
                builder.WriteLine("static void FFHR(HRESULT hr)");
                builder.OpenScope();
                builder.WriteLine("if (hr != S_OK)");
                builder.OpenScope();
                builder.WriteLine("RoFailFastWithErrorContext(hr);");
                builder.CloseScope();
                builder.CloseScope();
                builder.WriteLine();
            }

            // Write the constructor for the AnimatedVisual class.
            builder.UnIndent();
            builder.WriteLine("public:");
            builder.Indent();

            // Constructor.
            builder.WriteBreakableLine($"{info.ClassName}(", CommaSeparate(GetConstructorParameters(info)), ")");
            builder.Indent();

            // Initializer list.
            builder.WriteLine(": _c(compositor)");
            if (SourceInfo.IsThemed)
            {
                builder.WriteLine($", {SourceInfo.ThemePropertiesFieldName}(themeProperties)");
            }

            // Initialize the image surfaces.
            foreach (var n in info.LoadedImageSurfaceNodes)
            {
                builder.WriteLine($", {n.FieldName}({_s.CamelCase(n.Name)})");
            }

            // Instantiate the reusable ExpressionAnimation.
            builder.WriteLine($", {SourceInfo.ReusableExpressionAnimationFieldName}(compositor->CreateExpressionAnimation())");

            builder.UnIndent();

            builder.OpenScope();
            if (SourceInfo.UsesCanvasEffects ||
                SourceInfo.UsesCanvasGeometry)
            {
                builder.WriteLine("FFHR(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, _d2dFactory.GetAddressOf()));");
            }

            // Instantiate the root. This will cause the whole Visual tree to be built and animations started.
            builder.WriteLine("const auto _ = Root();");
            builder.CloseScope();

            // Write the destructor. This is how CX implements IClosable/IDisposable.
            builder.WriteLine();

            // CX doesn't need to do anything - the root object will be disposed
            // as a result of this object being cleaned up.
            builder.WriteLine($"virtual ~{info.ClassName}() {{ }}");

            // Write the members on IAnimatedVisual.
            builder.WriteLine();
            {
                var propertyImplBuilder = new CodeBuilder();
                propertyImplBuilder.WriteLine($"return {{ {_s.TimeSpan(SourceInfo.DurationTicksFieldName)} }};");
                WritePropertyImpl(builder, isVirtual: true, "TimeSpan", "Duration", propertyImplBuilder);
            }

            {
                var propertyImplBuilder = new CodeBuilder();
                propertyImplBuilder.WriteLine("return _root;");
                WritePropertyImpl(builder, isVirtual: true, "Visual^", "RootVisual", propertyImplBuilder);
            }

            {
                var propertyImplBuilder = new CodeBuilder();
                propertyImplBuilder.WriteLine($"return {_s.Vector2(SourceInfo.CompositionDeclaredSize)};");
                WritePropertyImpl(builder, isVirtual: true, "float2", "Size", propertyImplBuilder);
            }

            if (info.ImplementCreateAndDestroyMethods)
            {
                builder.WriteLine($"public void {CreateAnimationsMethod}()");
                builder.OpenScope();
                builder.WriteCodeBuilder(createAnimations);
                builder.CloseScope();
                builder.WriteLine();

                builder.WriteLine($"public void {DestroyAnimationsMethod}()");
                builder.OpenScope();
                builder.WriteCodeBuilder(destroyAnimations);
                builder.CloseScope();
                builder.WriteLine();
            }

            WriteIsRuntimeCompatibleMethod(builder, info);

            // Close the scope for the instantiator class.
            builder.CloseScopeWithSemicolon();
        }

        void WritePropertyImpl(
                CodeBuilder builder,
                bool isVirtual,
                string returnType,
                string propertyName,
                CodeBuilder getImplementation)
        {
            builder.WriteLine($"property {returnType} {propertyName}");
            builder.OpenScope();
            builder.WriteLine($"{(isVirtual ? "virtual " : string.Empty)}{returnType} get()");
            builder.OpenScope();
            builder.WriteCodeBuilder(getImplementation);
            builder.CloseScope();
            builder.CloseScope();
            builder.WriteLine();
        }

        /// <inheritdoc/>
        protected override void WriteCanvasGeometryPathFactory(CodeBuilder builder, CanvasGeometry.Path obj, string typeName, string fieldName)
        {
            builder.WriteLine($"{typeName} result;");

            // D2D Setup
            builder.WriteLine("ComPtr<ID2D1PathGeometry> path;");
            builder.WriteLine("FFHR(_d2dFactory->CreatePathGeometry(&path));");
            builder.WriteLine("ComPtr<ID2D1GeometrySink> sink;");
            builder.WriteLine("FFHR(path->Open(&sink));");

            if (obj.FilledRegionDetermination != CanvasFilledRegionDetermination.Alternate)
            {
                builder.WriteLine($"sink->SetFillMode({_s.FilledRegionDetermination(obj.FilledRegionDetermination)});");
            }

            foreach (var command in obj.Commands)
            {
                switch (command.Type)
                {
                    case CanvasPathBuilder.CommandType.BeginFigure:
                        // Assume D2D1_FIGURE_BEGIN_FILLED
                        builder.WriteLine($"sink->BeginFigure({_s.Vector2(((CanvasPathBuilder.Command.BeginFigure)command).StartPoint)}, D2D1_FIGURE_BEGIN_FILLED);");
                        break;
                    case CanvasPathBuilder.CommandType.EndFigure:
                        builder.WriteLine($"sink->EndFigure({_s.CanvasFigureLoop(((CanvasPathBuilder.Command.EndFigure)command).FigureLoop)});");
                        break;
                    case CanvasPathBuilder.CommandType.AddLine:
                        builder.WriteLine($"sink->AddLine({_s.Vector2(((CanvasPathBuilder.Command.AddLine)command).EndPoint)});");
                        break;
                    case CanvasPathBuilder.CommandType.AddCubicBezier:
                        var cb = (CanvasPathBuilder.Command.AddCubicBezier)command;
                        builder.WriteLine($"sink->AddBezier({{ {_s.Vector2(cb.ControlPoint1)}, {_s.Vector2(cb.ControlPoint2)}, {_s.Vector2(cb.EndPoint)} }});");
                        break;
                    default:
                        throw new InvalidOperationException();
                }
            }

            builder.WriteLine("FFHR(sink->Close());");
            builder.WriteLine("GeoSource* rawResult = new GeoSource(path.Get());");
            builder.WriteLine($"result = {FieldAssignment(fieldName)}rawResult;");
            builder.WriteLine("rawResult->Release();");
        }

        /// <inheritdoc/>
        protected override void WriteCanvasGeometryCombinationFactory(CodeBuilder builder, CanvasGeometry.Combination obj, string typeName, string fieldName)
        {
            builder.WriteLine($"{typeName} result;");
            builder.WriteLine("ID2D1Geometry *geoA = nullptr, *geoB = nullptr;");
            builder.WriteLine($"{CallFactoryFor(obj.A)}->GetGeometry(&geoA);");
            builder.WriteLine($"{CallFactoryFor(obj.B)}->GetGeometry(&geoB);");
            builder.WriteLine("ComPtr<ID2D1PathGeometry> path;");
            builder.WriteLine("FFHR(_d2dFactory->CreatePathGeometry(&path));");
            builder.WriteLine("ComPtr<ID2D1GeometrySink> sink;");
            builder.WriteLine("FFHR(path->Open(&sink));");
            builder.WriteLine($"FFHR(geoA->CombineWithGeometry(");
            builder.Indent();
            builder.WriteLine($"geoB,");
            builder.WriteLine($"{_s.CanvasGeometryCombine(obj.CombineMode)},");
            builder.WriteLine($"{_s.Matrix3x2(obj.Matrix)},");
            builder.WriteLine($"sink.Get()));");
            builder.UnIndent();
            builder.WriteLine("geoA->Release();");
            builder.WriteLine("geoB->Release();");
            builder.WriteLine("FFHR(sink->Close());");
            builder.WriteLine($"result = {FieldAssignment(fieldName)}new GeoSource(path.Get());");
        }

        /// <inheritdoc/>
        protected override void WriteCanvasGeometryEllipseFactory(CodeBuilder builder, CanvasGeometry.Ellipse obj, string typeName, string fieldName)
        {
            builder.WriteLine($"{typeName} result;");
            builder.WriteLine("ComPtr<ID2D1EllipseGeometry> ellipse;");
            builder.WriteLine("FFHR(_d2dFactory->CreateEllipseGeometry(");
            builder.Indent();
            builder.WriteLine($"D2D1::Ellipse({{{_s.Float(obj.X)},{_s.Float(obj.Y)}}}, {_s.Float(obj.RadiusX)}, {_s.Float(obj.RadiusY)}),");
            builder.WriteLine("&ellipse));");
            builder.UnIndent();
            builder.WriteLine($"result = {FieldAssignment(fieldName)}new GeoSource(ellipse.Get());");
        }

        /// <inheritdoc/>
        protected override void WriteCanvasGeometryRoundedRectangleFactory(CodeBuilder builder, CanvasGeometry.RoundedRectangle obj, string typeName, string fieldName)
        {
            builder.WriteLine($"{typeName} result;");
            builder.WriteLine("ComPtr<ID2D1RoundedRectangleGeometry> rect;");
            builder.WriteLine("FFHR(_d2dFactory->CreateRoundedRectangleGeometry(");
            builder.Indent();
            builder.WriteLine($"D2D1::RoundedRect({{{_s.Float(obj.X)},{_s.Float(obj.Y)}}}, {_s.Float(obj.RadiusX)}, {_s.Float(obj.RadiusY)}),");
            builder.WriteLine("&rect));");
            builder.UnIndent();
            builder.WriteLine($"result = {FieldAssignment(fieldName)}new GeoSource(rect.Get());");
        }

        /// <inheritdoc/>
        protected override void WriteCanvasGeometryTransformedGeometryFactory(CodeBuilder builder, CanvasGeometry.TransformedGeometry obj, string typeName, string fieldName)
        {
            builder.WriteLine($"{typeName} result;");
            builder.WriteLine("ID2D1Geometry *geoA = nullptr;");
            builder.WriteLine("ID2D1TransformedGeometry *transformed;");
            builder.WriteLine($"D2D1_MATRIX_3X2_F transformMatrix{_s.Matrix3x2(obj.TransformMatrix)};");
            builder.WriteLine();
            builder.WriteLine($"{CallFactoryFor(obj.SourceGeometry)}->GetGeometry(&geoA);");
            builder.WriteLine("FFHR(_d2dFactory->CreateTransformedGeometry(geoA, transformMatrix, &transformed));");
            builder.WriteLine("geoA->Release();");
            builder.WriteLine($"result = {FieldAssignment(fieldName)}new GeoSource(transformed);");
        }

        /// <inheritdoc/>
        protected override void WriteCanvasGeometryGroupFactory(CodeBuilder builder, CanvasGeometry.Group obj, string typeName, string fieldName)
        {
            builder.WriteLine($"ComPtr<ID2D1Geometry> geometries[{obj.Geometries.Length}];");
            builder.OpenScope();
            for (var i = 0; i < obj.Geometries.Length; i++)
            {
                var geometry = obj.Geometries[i];
                builder.WriteLine($"{CallFactoryFor(geometry)}.Get()->GetGeometry(&geometries[{i}]);");
            }

            builder.CloseScope();
            builder.WriteLine($"{typeName} result;");
            builder.WriteLine("ComPtr<ID2D1GeometryGroup> group;");
            builder.WriteLine("FFHR(_d2dFactory->CreateGeometryGroup(");
            builder.Indent();
            builder.WriteLine($"{_s.FilledRegionDetermination(obj.FilledRegionDetermination)},");
            builder.WriteLine("geometries[0].GetAddressOf(),");
            builder.WriteLine($"{obj.Geometries.Length},");
            builder.WriteLine("&group));");
            builder.UnIndent();
            builder.WriteLine($"result = {FieldAssignment(fieldName)}new GeoSource(group.Get());");
        }

        string QualifiedTypeName(PropertySetValueType propertySetValueType)
            => propertySetValueType switch
            {
                PropertySetValueType.Color => $"Windows::UI::Color",
                _ => TypeName(propertySetValueType),
            };

        IEnumerable<string> GetConstructorParameters(IAnimatedVisualInfo info)
        {
            yield return "Compositor^ compositor";

            if (SourceInfo.IsThemed)
            {
                yield return "CompositionPropertySet^ themeProperties";
            }

            foreach (var loadedImageSurfaceNode in info.LoadedImageSurfaceNodes)
            {
                yield return $"{loadedImageSurfaceNode.TypeName}^ {_s.CamelCase(loadedImageSurfaceNode.Name)}";
            }
        }

        static string GeoSourceClass => @"
class GeoSource final :
    public ABI::Windows::Graphics::IGeometrySource2D,
    public ABI::Windows::Graphics::IGeometrySource2DInterop
 {
    ULONG _cRef;
    ComPtr<ID2D1Geometry> _cpGeometry;

public:
    GeoSource(ID2D1Geometry* pGeometry)
        : _cRef(1)
        , _cpGeometry(pGeometry)
    { }

    IFACEMETHODIMP QueryInterface(REFIID iid, void ** ppvObject) override
    {
        if (iid == __uuidof(ABI::Windows::Graphics::IGeometrySource2DInterop))
        {
            AddRef();
            *ppvObject = static_cast<ABI::Windows::Graphics::IGeometrySource2DInterop*>(this);
            return S_OK;
        }
        return E_NOINTERFACE;
    }

    IFACEMETHODIMP_(ULONG) AddRef() override
    {
        return InterlockedIncrement(&_cRef);
    }

    IFACEMETHODIMP_(ULONG) Release() override
    {
        ULONG cRef = InterlockedDecrement(&_cRef);
        if (cRef == 0)
        {
            delete this;
        }
        return cRef;
    }

    IFACEMETHODIMP GetIids(ULONG*, IID**) override
    {
        return E_NOTIMPL;
    }

    IFACEMETHODIMP GetRuntimeClassName(HSTRING*) override
    {
        return E_NOTIMPL;
    }

    IFACEMETHODIMP GetTrustLevel(::TrustLevel*) override
    {
        return E_NOTIMPL;
    }

    IFACEMETHODIMP GetGeometry(ID2D1Geometry** value) override
    {
        *value = _cpGeometry.Get();
        (*value)->AddRef();
        return S_OK;
    }

    IFACEMETHODIMP TryGetGeometryUsingFactory(ID2D1Factory*, ID2D1Geometry**) override
    {
        return E_NOTIMPL;
    }
};
";

        static string CompositeEffectClass =>
@"

enum CanvasComposite : int
{
    SourceOver = 0,
    DestinationOver = 1,
    SourceIn = 2,
    DestinationIn = 3,
    SourceOut = 4,
    DestinationOut = 5,
    SourceAtop = 6,
    DestinationAtop = 7,
    Xor = 8,
    Add = 9,
    Copy = 10,
    BoundedCopy = 11,
    MaskInvert = 12,
};

// This class is a substitute for the Microsoft::Graphics::Canvas::Effects::CompositeEffect
// class so that composite effects can be used with 
// Windows::UI::Composition::CompositionEffectBrush without requiring Win2d.
class CompositeEffect final :
    public ABI::Windows::Graphics::Effects::IGraphicsEffect,
    public ABI::Windows::Graphics::Effects::IGraphicsEffectSource,
    public ABI::Windows::Graphics::Effects::IGraphicsEffectD2D1Interop
{
    ULONG m_cRef{};
    CanvasComposite m_mode{};
    Microsoft::WRL::Wrappers::HString m_name{};
    std::vector<Microsoft::WRL::ComPtr<IGraphicsEffectSource>> m_sources{};

public:
    void SetMode(CanvasComposite mode) { m_mode = mode; }

    void AddSource(IGraphicsEffectSource* source)
    {
        m_sources.emplace_back(Microsoft::WRL::ComPtr<IGraphicsEffectSource>(source));
    }

    // IGraphicsEffect.
    IFACEMETHODIMP get_Name(HSTRING* name) override { return m_name.CopyTo(name); }
    IFACEMETHODIMP put_Name(HSTRING name) override { return m_name.Set(name); }

    // IGraphicsEffectD2D1Interop.
    IFACEMETHODIMP GetEffectId(GUID* id) override 
    { 
        if (id != nullptr)
        {
            // CLSID_D2D1Composite.
            *id = { 0x48fc9f51, 0xf6ac, 0x48f1, { 0x8b, 0x58, 0x3b, 0x28, 0xac, 0x46, 0xf7, 0x6d } };
        }

        return S_OK; 
    }

    // IGraphicsEffectD2D1Interop.
    IFACEMETHODIMP GetSourceCount(UINT* count) override
    {
        if (count != nullptr)
        {
            *count = static_cast<UINT>(m_sources.size());
        }

        return S_OK;
    }

    // IGraphicsEffectD2D1Interop.
    IFACEMETHODIMP GetSource(
        UINT index, 
        IGraphicsEffectSource** source) override
    {
        if (index >= m_sources.size() ||
            source == nullptr)
        {
            return E_INVALIDARG;
        }

        *source = m_sources.at(index).Get();
        (*source)->AddRef();

        return S_OK;
    }

    // IGraphicsEffectD2D1Interop.
    IFACEMETHODIMP GetPropertyCount(UINT * count) override { *count = 1; return S_OK; }

    // IGraphicsEffectD2D1Interop.
    IFACEMETHODIMP GetProperty(
        UINT index, 
        ABI::Windows::Foundation::IPropertyValue ** value) override
    {
        Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IPropertyValueStatics> propertyValueFactory;
        Microsoft::WRL::Wrappers::HStringReference activatableClassId{ RuntimeClass_Windows_Foundation_PropertyValue };
        HRESULT hr = ABI::Windows::Foundation::GetActivationFactory(activatableClassId.Get(), &propertyValueFactory);

        if (SUCCEEDED(hr))
        {
            switch (index)
            {
                case D2D1_COMPOSITE_PROP_MODE: 
                    return propertyValueFactory->CreateUInt32(m_mode, (IInspectable**)value);
                default: 
                    return E_INVALIDARG;
            }
        }

        return hr;
    }

    // IGraphicsEffectD2D1Interop.
    IFACEMETHODIMP GetNamedPropertyMapping(
        LPCWSTR, 
        UINT*,
        ABI::Windows::Graphics::Effects::GRAPHICS_EFFECT_PROPERTY_MAPPING*) override
    {
        return E_INVALIDARG;
    }

    // IUnknown.
    IFACEMETHODIMP QueryInterface(
        REFIID iid,
        void ** ppvObject) override
    {
        if (ppvObject != nullptr)
        {
            *ppvObject = nullptr;

            if (iid == __uuidof(IUnknown))
            {
                *ppvObject = static_cast<IUnknown*>(static_cast<IGraphicsEffect*>(this));
            }
            else if (iid == __uuidof(IInspectable))
            {
                *ppvObject = static_cast<IInspectable*>(static_cast<IGraphicsEffect*>(this));
            }
            else if (iid == __uuidof(IGraphicsEffect))
            {
                *ppvObject = static_cast<IGraphicsEffect*>(this);
            }
            else if (iid == __uuidof(IGraphicsEffectSource))
            {
                *ppvObject = static_cast<IGraphicsEffectSource*>(this);
            }
            else if (iid == __uuidof(IGraphicsEffectD2D1Interop))
            {
                *ppvObject = static_cast<IGraphicsEffectD2D1Interop*>(this);
            }

            if (*ppvObject != nullptr)
            {
                AddRef();
                return S_OK;
            }
        }

        return E_NOINTERFACE;
    }

    // IUnknown.
    IFACEMETHODIMP_(ULONG) AddRef() override
    {
        return InterlockedIncrement(&m_cRef);
    }

    // IUnknown.
    IFACEMETHODIMP_(ULONG) Release() override
    {
        ULONG cRef = InterlockedDecrement(&m_cRef);
        if (cRef == 0)
        {
            delete this;
        }

        return cRef;
    }

    // IInspectable.
    IFACEMETHODIMP GetIids(
        ULONG* iidCount,
        IID** iids) override
    {
        if (iidCount != nullptr)
        {
            *iidCount = 0;
        }

        if (iids != nullptr)
        {
            *iids = nullptr;
        }

        return E_NOTIMPL;
    }

    // IInspectable.
    IFACEMETHODIMP GetRuntimeClassName(
        HSTRING* /*runtimeName*/) override
    {
        return E_NOTIMPL;
    }

    // IInspectable.
    IFACEMETHODIMP GetTrustLevel(
        ::TrustLevel* trustLvl) override
    {
        if (trustLvl != nullptr)
        {
            *trustLvl = BaseTrust;
        }

        return S_OK;
    }
};
";

        static string GaussianBlurEffectClass =>
@"

// This class is a substitute for the Microsoft::Graphics::Canvas::Effects::GaussianBlurEffect
// class so that composite effects can be used with 
// Windows::UI::Composition::CompositionEffectBrush without requiring Win2d.
class GaussianBlurEffect final :
    public ABI::Windows::Graphics::Effects::IGraphicsEffect,
    public ABI::Windows::Graphics::Effects::IGraphicsEffectSource,
    public ABI::Windows::Graphics::Effects::IGraphicsEffectD2D1Interop
{
    ULONG m_cRef{};
    Microsoft::WRL::Wrappers::HString m_name{};
    float m_blurAmount = 3.0f;
    Microsoft::WRL::ComPtr<IGraphicsEffectSource> m_source{};

public:
    void put_BlurAmount(float amount) { m_blurAmount = amount; }
    void put_Source(IGraphicsEffectSource* source) { m_source = source; }

    // IGraphicsEffect.
    IFACEMETHODIMP get_Name(HSTRING* name) override { return m_name.CopyTo(name); }
    IFACEMETHODIMP put_Name(HSTRING name) override { return m_name.Set(name); }

    // IGraphicsEffectD2D1Interop.
    IFACEMETHODIMP GetEffectId(GUID* id) override 
    { 
        if (id != nullptr)
        {
            // CLSID_D2D1GaussianBlur.
            *id = { 0x1feb6d69, 0x2fe6, 0x4ac9, { 0x8c, 0x58, 0x1d, 0x7f, 0x93, 0xe7, 0xa6, 0xa5 } };
        }

        return S_OK; 
    }

    // IGraphicsEffectD2D1Interop.
    IFACEMETHODIMP GetSourceCount(UINT* count) override
    {
        if (count != nullptr)
        {
            *count = 1;
        }

        return S_OK;
    }

    // IGraphicsEffectD2D1Interop.
    IFACEMETHODIMP GetSource(
        UINT index, 
        IGraphicsEffectSource** source) override
    {
        if (index != 0 ||
            source == nullptr)
        {
            return E_INVALIDARG;
        }

        *source = m_source.Get();
        (*source)->AddRef();

        return S_OK;
    }

    // IGraphicsEffectD2D1Interop.
    IFACEMETHODIMP GetPropertyCount(UINT * count) override { *count = 3; return S_OK; }

    // IGraphicsEffectD2D1Interop.
    IFACEMETHODIMP GetProperty(
        UINT index, 
        ABI::Windows::Foundation::IPropertyValue ** value) override
    {
        Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IPropertyValueStatics> propertyValueFactory;
        Microsoft::WRL::Wrappers::HStringReference activatableClassId{ RuntimeClass_Windows_Foundation_PropertyValue };
        HRESULT hr = ABI::Windows::Foundation::GetActivationFactory(activatableClassId.Get(), &propertyValueFactory);

        if (SUCCEEDED(hr))
        {
            switch (index)
            {
                case D2D1_GAUSSIANBLUR_PROP_BORDER_MODE: 
                    return propertyValueFactory->CreateUInt32(0, (IInspectable**)value);
                case D2D1_GAUSSIANBLUR_PROP_OPTIMIZATION: 
                    return propertyValueFactory->CreateUInt32(1, (IInspectable**)value);
                case D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION: 
                    return propertyValueFactory->CreateSingle(m_blurAmount, (IInspectable**)value);
                default: 
                    return E_INVALIDARG;
            }
        }

        return hr;
    }

    // IGraphicsEffectD2D1Interop.
    IFACEMETHODIMP GetNamedPropertyMapping(
        LPCWSTR, 
        UINT*,
        ABI::Windows::Graphics::Effects::GRAPHICS_EFFECT_PROPERTY_MAPPING*) override
    {
        return E_INVALIDARG;
    }

    // IUnknown.
    IFACEMETHODIMP QueryInterface(
        REFIID iid,
        void ** ppvObject) override
    {
        if (ppvObject != nullptr)
        {
            *ppvObject = nullptr;

            if (iid == __uuidof(IUnknown))
            {
                *ppvObject = static_cast<IUnknown*>(static_cast<IGraphicsEffect*>(this));
            }
            else if (iid == __uuidof(IInspectable))
            {
                *ppvObject = static_cast<IInspectable*>(static_cast<IGraphicsEffect*>(this));
            }
            else if (iid == __uuidof(IGraphicsEffect))
            {
                *ppvObject = static_cast<IGraphicsEffect*>(this);
            }
            else if (iid == __uuidof(IGraphicsEffectSource))
            {
                *ppvObject = static_cast<IGraphicsEffectSource*>(this);
            }
            else if (iid == __uuidof(IGraphicsEffectD2D1Interop))
            {
                *ppvObject = static_cast<IGraphicsEffectD2D1Interop*>(this);
            }

            if (*ppvObject != nullptr)
            {
                AddRef();
                return S_OK;
            }
        }

        return E_NOINTERFACE;
    }

    // IUnknown.
    IFACEMETHODIMP_(ULONG) AddRef() override
    {
        return InterlockedIncrement(&m_cRef);
    }

    // IUnknown.
    IFACEMETHODIMP_(ULONG) Release() override
    {
        ULONG cRef = InterlockedDecrement(&m_cRef);
        if (cRef == 0)
        {
            delete this;
        }

        return cRef;
    }

    // IInspectable.
    IFACEMETHODIMP GetIids(
        ULONG* iidCount,
        IID** iids) override
    {
        if (iidCount != nullptr)
        {
            *iidCount = 0;
        }

        if (iids != nullptr)
        {
            *iids = nullptr;
        }

        return E_NOTIMPL;
    }

    // IInspectable.
    IFACEMETHODIMP GetRuntimeClassName(
        HSTRING* /*runtimeName*/) override
    {
        return E_NOTIMPL;
    }

    // IInspectable.
    IFACEMETHODIMP GetTrustLevel(
        ::TrustLevel* trustLvl) override
    {
        if (trustLvl != nullptr)
        {
            *trustLvl = BaseTrust;
        }

        return S_OK;
    }
};
";
    }
}
